package com.jiang.operator;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.jiang.entity.*;
import com.jiang.firefly.Particle;
import com.jiang.metadata.Metadata;
import com.jiang.storage1.Node;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spark.utils.StringUtils;

import java.awt.color.CMMException;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;

public class BlockChain {
    private static final Logger LOGGER = LoggerFactory.getLogger(BlockChain.class);

    public static List<Block> blockChain = new LinkedList<Block>();
    private static final String VERSION = "1.1";//第二版的区块链
    //节点，各个节点
    public static List<Node> nodeList = new ArrayList<Node>();
    public static List<Node> canNode = new ArrayList<Node>();//候选节点
    private static List<String> uDataList = new ArrayList<String>();

    public static void main(String[] args) throws IOException, InterruptedException {
        final Gson gson = new GsonBuilder().create();
        final Gson prettyGson = new GsonBuilder().setPrettyPrinting().create();
        WorkProof workProof = null;
        //设置服务器的端口
        int port = 8015;
        /**
         * 该部分为，在这个8015端口启动监听，等待访问该接口的客户端套接字访问
         * 然后生成一个对等线程
         */
        LOGGER.info("Starting peer network.....");
        PeerNetwork peerNetwork = new PeerNetwork(port);//传入端口
        peerNetwork.start();//启动对等网络，内容是启动服务器监听端口，监听各个客户端的套接字
        LOGGER.info("[   Node is started in port: "+port+"   ]");

        /**
         * 该部分为，在8016端口启动监听，等待访问该端口的客户端套接字访问
         * 然后生成一个远程访问线程
         */
        LOGGER.info("Starting RPC daemon... ");
        RpcServer rpcAgent = new RpcServer(port+1);
        rpcAgent.start(); //启动了rpc服务，这里换了一个端口8016.监听客户端的请求
        LOGGER.info("[    RPC agent is started in port: "+(port+1)+"   ]");

        /**
         * 该部分生成区块链系统中的节点列表，若还没有该文件，则向里面写入本机的ip以及端口，也就是套接字
         * 如果存在该文件，也就是写入过了，则需要读取该文件中每个区块链节点，获取其ip地址，如果匹配地址是本机
         * 则将该套接字放入区块链节点集合中，并新建客户端套接字连接该服务器节点上
         */
        List<String> peers = new ArrayList<String>();
        //这个文件很重要，相当于节点列表
        File peerFile = new File("peer.list");
        //判断该文件是否存在
        if(!peerFile.exists()){
            //不存在时，新建
            String host = InetAddress.getLocalHost().toString();//获得主机IP
            FileUtils.writeStringToFile(peerFile, host+":"+port, StandardCharsets.UTF_8, true);//将该数据写入文件中
        }else {
            //如果文件存在，就不需要写
            for(String peer: FileUtils.readLines(peerFile, StandardCharsets.UTF_8)){
                //按照UTF-8格式读取文件
                String[] addr = peer.split(":");
                if(CommonUtils.isLocal(addr[0]) && String.valueOf(port).equals(addr[1])){//判断该IP是否和本机ip相同
                    //只比较端口的前面
                    peers.add(peer);
                    continue;
                }
                peers.add(peer);
                //raw ipv4
                peerNetwork.connect(addr[0], Integer.parseInt(addr[1]));//将客户端的ip和port传输进去，连接

            }
        }
        //新建文件初始节点资源量
        File storageFile = new File("storage.list");
        if(storageFile.exists()){
            //如果该文件存在的话
            for(String storage: FileUtils.readLines(storageFile, StandardCharsets.UTF_8)){
                String[] nodeSt = storage.split(":");
                nodeList.add(new Node());
                nodeList.get(nodeList.size()-1).setId(nodeSt[0]);
                String[] res = nodeSt[1].split("/");
                nodeList.get(nodeList.size()-1).setUse_disk(Integer.parseInt(res[0]));
                nodeList.get(nodeList.size()-1).setDisk(Integer.parseInt(res[1]));
                String[] loca = nodeSt[2].split("/");
                nodeList.get(nodeList.size()-1).setX(Integer.parseInt(loca[0]));
                nodeList.get(nodeList.size()-1).setY(Integer.parseInt(loca[1]));
                System.out.println(nodeList.get(nodeList.size()-1));
            }

        }
        /**
         * 保存数据，用户的数据
         */
        File uDataFile = new File("data.bin");
        /**
         * 新建区块信息文件，如果该文件不存在，则创建创世区块。并写入文件中，否则文件存在，将
         * 区块信息读取出来，反序列化，放到链中，这样代表，一些区块已经生成了。
         * 之后，广播，对于每个连接的对等网络客户端，输出信息
         */




        File dataFile = new File("block.bin");
        if(!dataFile.exists()){
            //文件不存在
            //创建创世区块
            String[] vac = {"54", "12", "52"};
            Block genesisBlock = new Block();
            genesisBlock.setIndex(0);
            genesisBlock.setPrevHash("");
            genesisBlock.setTimestamp("2020-06-03 07:30:24");
            genesisBlock.setVac(vac);
            genesisBlock.setMerkleRoot(BlockUtils.hashMerkle(Arrays.asList(vac)));
            genesisBlock.setHash(BlockUtils.calculateHash(genesisBlock));
            blockChain.add(genesisBlock);
            //将创世区块写入数据文件中
            FileUtils.writeStringToFile(dataFile, gson.toJson(genesisBlock), StandardCharsets.UTF_8, true);
        }else {
            //文件存在，说明已经包括创世区块
            //读出文件中所有行的数据
            List<String> list = FileUtils.readLines(dataFile, StandardCharsets.UTF_8);
            for(String line:list){
                blockChain.add(gson.fromJson(line, Block.class));//从文件读取数据，反序列化，生成区块，加入区块链
            }
        }
        TimeUnit.MILLISECONDS.sleep(2);
        //pretty print
        LOGGER.info(prettyGson.toJson(blockChain));

        int bestHeight = blockChain.size();//获得区块链的长度
        //建立socket连接之后，给大家广播握手
        peerNetwork.broadcast("VERSION "+bestHeight+" "+VERSION);

        boolean dataFlag = false;
        int numDiff = 0;

        //p2p通讯
        /**
         * 一直循环不停，一直执行
         */
        while(true){
            /**
             *查看对等网络中的对等节点是否和文件中保存的一样，如果文件中少的话，则需要将该节点信息写入文件中
             * 并清除该对等网络中的对等节点列表
             */
            for(String peer : peerNetwork.peers){
                if(!peers.contains(peer)){
                    peers.add(peer);
                    LOGGER.info("文件中没有该对等节点，add peer to file:"+peer);
                    //下面true代表接着在文件中添加，不用清除
                    FileUtils.writeStringToFile(peerFile, "\n"+peer, StandardCharsets.UTF_8, true);
                }
            }
            peerNetwork.peers.clear();//清除该对等网络中的列表

            /**
             *对于每个客户端套接字，如果该线程为空或者写线程为空，则停止所有线程
             * 每个客户端需要读取缓冲区的数据，如果数据为空，则停止所有线程
             * 否则，输出该线程中的输入的每条命令，对于每个命令，获取空格的位置，也就是截取重要的信息
             * 分为两部分，cmd和payload,根据cmd，来决定应该回复客户端什么数据
             *
             * 这些更多的是对于对等网络的每个节点，同步一些节点操作，区块等等
             * 更多的，通过8015端口来进行内部操作，一些命令
             */
            for(PeerThread pt : peerNetwork.peerThreads){

                //对待每个客户端
                if(pt == null || pt.peerReader == null){
                    break;
                }
                List<String> dataList = pt.peerReader.readData();//从每个客户端获取输入的命令
                if(dataList == null){
                    LOGGER.info("None,没有数据");
                    System.exit(-5);
                    break;
                }
                for(String data:dataList){
                    LOGGER.info("[P2P] COMMAND:: "+data);//每个命令
                    int flag = data.indexOf(' ');
                    String cmd = flag >= 0 ? data.substring(0, flag) : data;
                    //上一句也就是如果这个命令中可以找到空格，就把空格之前的命令截取下来赋值给cmd，否则将全部的data作为命令
                    String payload = flag >= 0 ? data.substring(flag+1) : "";
                    if(StringUtils.isNotBlank(cmd)){
                        //检查该命令是否为null或者为空
                        if("VERACK".equalsIgnoreCase(cmd)){
                            //该命令代表对方确认知道了，并获得区块链长度
                            String[] parts = payload.split(" ");
                            bestHeight = Integer.parseInt(parts[0]);
                        }else if("VERSION".equalsIgnoreCase(cmd)){
                            //这属于对手发送过来握手信息
                            //需要获取区块链长度以及版本号
                            String[] parts = payload.split(" ");
                            bestHeight = Integer.parseInt(parts[0]);
                            //我方需要回复收到
                            pt.peerWriter.write("VERACK "+blockChain.size()+" "+blockChain.get(blockChain.size()-1).getHash());

                        }else if("DOU_BLOCK".equalsIgnoreCase(cmd)){
                            //说明网络中出现了重复的区块
                            Block newBlock = gson.fromJson(payload, Block.class);
                            if(!newBlock.getHash().equals(blockChain.get(blockChain.size()-1).getHash())){
                                //说明有一个不一样
                                numDiff++;
                            }
                            if(numDiff > nodeList.size()/2){
                                blockChain.remove(blockChain.size()-1);
                                blockChain.add(newBlock);
                                //文件中
                                FileUtils.writeStringToFile(dataFile, gson.toJson(blockChain.get(0)), StandardCharsets.UTF_8);
                                for(int t = 1;t < blockChain.size();t++){
                                    FileUtils.writeStringToFile(dataFile, "\n"+gson.toJson(blockChain.get(t)), StandardCharsets.UTF_8,true);
                                }
                                numDiff = 0;
                            }

                        } else if("NEWBLOCK".equalsIgnoreCase(cmd)) {
                            Block newBlock = gson.fromJson(payload, Block.class);
                            if(blockChain.size()-1 == newBlock.getIndex()){
                                //重复了
                                numDiff = 0;
                                peerNetwork.broadcast("DOU_BLOCK " + gson.toJson(blockChain.get(blockChain.size()-1)));
                            }
                            if (!blockChain.contains(newBlock) && blockChain.get(blockChain.size()-1).getIndex() != newBlock.getIndex()) {
                                //当自己没有这个区块时，怎样阻断自己的进程
                                if (BlockUtils.isBlockValid(newBlock, blockChain.get(blockChain.size() - 1))) {
                                    //区块检验合法之后，就放入区块链中
                                    WorkProof.flag = true;
                                    LOGGER.info("Attempting to add Block:" + payload);
                                    blockChain.add(newBlock);
                                    LOGGER.info("Added block " + newBlock.getIndex() + " with hash: [" + newBlock.getHash() + "]");
                                    FileUtils.writeStringToFile(dataFile, "\n" + gson.toJson(newBlock), StandardCharsets.UTF_8, true);
                                } else {
                                    //不合法
                                    LOGGER.info("RPC 500: Invalid vac Error");
                                }
                            }
                        }else if("BLOCK".equalsIgnoreCase(cmd)){
                            //检验区块
                            Block newBlock = gson.fromJson(payload, Block.class);
                            if(!blockChain.contains(newBlock) && blockChain.get(blockChain.size()-1).getIndex() != newBlock.getIndex()){
                                LOGGER.info("Attempting to add Block:"+payload);
                                //校验区块，如果成功，将其写入本地区块链
                                blockChain.add(newBlock);
                                LOGGER.info("Added block "+newBlock.getIndex()+" with hash: ["+newBlock.getHash()+"]");
                                FileUtils.writeStringToFile(dataFile, "\n"+gson.toJson(newBlock), StandardCharsets.UTF_8,true);
                                peerNetwork.broadcast("BLOCK "+payload);
                            }


                        }else if("MINE".equalsIgnoreCase(cmd)){
                            //进行多个节点挖掘
                            int indexID = -1;
                            WorkProof.flag = false;
                            String[] vac = payload.split(" ");
                            LOGGER.info(InetAddress.getLocalHost().getHostAddress());
                            for(int i = 0;i < nodeList.size();i++){
                                if(nodeList.get(i).getId().equals(InetAddress.getLocalHost().getHostAddress())){
                                    indexID = i;
                                    break;
                                }
                            }
                            workProof = new WorkProof(blockChain.get(blockChain.size()-1), vac,
                                    nodeList.get(indexID), peerNetwork, dataFile);
                            Thread workThread = new Thread(workProof);
                            workThread.start();
                            //如果成功，发送成功消息

                        }
                        else if("GET_BLOCK".equalsIgnoreCase(cmd)){
                            //该命令的意思就是，想要获得某区块，请求该区块
                            Block block = blockChain.get(Integer.parseInt(payload));
                            if(block != null){
                                //该区块非空
                                LOGGER.info("Sending block "+payload+" to peer");
                                pt.peerWriter.write("BLOCK "+gson.toJson(block));
                            }
                        }else if("GETDATA".equalsIgnoreCase(cmd)){
                            List<String> udataList = FileUtils.readLines(uDataFile, StandardCharsets.UTF_8);
                            for(String line:udataList){
                                if(payload.equals(line.split(" ")[0])){
                                    //怎么返回
                                    LOGGER.info("请求的数据，本节点拥有，发送！");
                                    pt.send("NEED_DATA "+line);
                                    break;
                                }
                            }

                        }else if("NEED_DATA".equalsIgnoreCase(cmd)){
                            if(dataFlag){
                                //说明还没接收到
                                dataFlag = false;//现在接收到了
                                LOGGER.info("Receive success! From "+pt.socket.getInetAddress());
                                LOGGER.info(payload);
                            }else {
                                LOGGER.info("已经接收到数据，不需要了");
                            }
                        }else if("DATA".equalsIgnoreCase(cmd)){
                            //对该数据项进行保存，该data吧，需要给那些
                            String[] data1 = payload.split(" ");
                            List<String> data2 = Arrays.asList(data1[data1.length-1].split("/"));
                            if(data2.contains(InetAddress.getLocalHost().getHostAddress())){
                                //如果保存的节点包括该节点，则将该数据保存下来，再看看本地有没有
                                if(!uDataFile.exists()){
                                    FileUtils.writeStringToFile(uDataFile, payload, StandardCharsets.UTF_8, true);
                                }else {
                                    FileUtils.writeStringToFile(uDataFile, "\n"+payload, StandardCharsets.UTF_8, true);
                                }
                                uDataList.add(payload);//再添加进uDataList
                            }
                            //还得对该节点的存储资源进行更新
                            for(int i = 0;i < nodeList.size();i++){
                                if(nodeList.get(i).getId().equals(InetAddress.getLocalHost().getHostAddress())){
                                    nodeList.get(i).setUse_disk(Integer.parseInt(data1[4]));
                                    nodeList.get(i).setData(data1[5]);
                                }
                            }
                        }else if("ADDR".equalsIgnoreCase(cmd)){
                            //客户端发来一个地址，如果对等节点列表中没有该节点，说明尚未连接
                            //需要建立连接
                            if(!peers.contains(payload)){
                                String peerAddr = payload.substring(0, payload.indexOf(":"));
                                int peerPort = Integer.parseInt(payload.substring(payload.indexOf(":")+1));
                                peerNetwork.connect(peerAddr, peerPort);
                                peers.add(payload);
                                PrintWriter out = new PrintWriter(peerFile);
                                for(int k = 0;k < peers.size();k++){
                                    out.println(peers.get(k));
                                }
                                out.close();
                            }
                        }else if("GET_ADDR".equalsIgnoreCase(cmd)){
                            Random random = new Random();
                            //该线程输出得到的随机peer地址
                            pt.peerWriter.write("ADDR "+peers.get(random.nextInt(peers.size())));
                        }

                    }
                }

            }
            /**
             * 查看本地的区块链长度，如果比当今最好的小，就说明短，有些区块没有同步
             * 需要进行同步，将剩下的区块，通过广播给其它的客户端，连接到对等网络的客户端，进行同步
             */
            int localHeight = blockChain.size();
            if(bestHeight > localHeight){
                LOGGER.info("Local blockchain height: "+ localHeight+" Best chain height: "+bestHeight);
                TimeUnit.MILLISECONDS.sleep(300);

                for(int i = localHeight;i < bestHeight;i++){
                    LOGGER.info("request get block["+i+"]...");
                    peerNetwork.broadcast("GET_BLOCK "+i);
                }

            }
            /**
             *这些访问8016端口的，称为远程访问连接，这些就是进行操作的地方，客户端输入
             *一些命令。可以查看区块的信息，另外可以新建区块等
             */

            for(RpcThread th : rpcAgent.rpcThreads){

                String request = th.req;
                if (request != null){
                    String[] parts = request.split(" ");
                    parts[0] = parts[0].toLowerCase();//都转化为小写
                    if("getinfo".equals(parts[0])){
                        String res = prettyGson.toJson(blockChain);
                        th.res = res;
                    }else if("getdata".equals(parts[0])){
                        //利用广播
                        List<String> udataList = FileUtils.readLines(uDataFile, StandardCharsets.UTF_8);
                        for(String line:udataList){
                            if(parts[1].equals(line.split(" ")[0])){
                                //本地有
                                th.res = line;
                                break;
                            }
                        }
                        if(th.res == null){
                            peerNetwork.broadcast("GETDATA "+parts[1]);
                            dataFlag = true;
                            th.res = "Please wait ..., requesting to other nodes!";
                        }

                    }else if("send".equals(parts[0])){
                        try {
                            WorkProof.flag = false;
                            /**
                             * 这个部分来截取信息
                             * 什么数据类型，地址，具体数据啥的
                             */
                            //send AirQuality/PM2.5 2018-02-19/09:28:32 LASAI/37.26,-24.22 Chaozhou 24 2013-12-02:31/2013-12-03:28/2013-12-04:34/2013-12-05:25/2013-12-06:26/2013-12-07:35
                            String[] parts2 = request.substring(5).split(" ");
                            //这个结果已经截出来了
                            /**
                             * parts[0]类型 parts[1]时间 parts[2]位置 parts[3]生产者签名 parts[4]数据大小
                             * 这四个原封不动
                             */
                            //算法选择存储的节点

                            //检查剩下的服务器有多少能够存储这个数据项
                            canNode.clear();
                            for(int j = 0;j < nodeList.size();j++){
                                if(Integer.parseInt(parts2[4]) <= nodeList.get(j).getDisk() - nodeList.get(j).getUse_disk()){
                                    canNode.add(nodeList.get(j));
                                }
                            }

                            Particle.dim = canNode.size();
                            Particle.uDataSize = Integer.parseInt(parts2[4]);
                            Particle.uLocation = parts2[2];
                            Particle e = new Particle();
                            e.init();
                            e.PSO(1000);//迭代1000次


                            //选择最终的几个设备
                            List<String> storeList = new ArrayList<String>();
                            for(int j = 0;j < e.gbest.getDims1().length;j++){
                                if(e.gbest.getDims1()[j] == 1){
                                    storeList.add(canNode.get(j).getId());
                                    canNode.get(j).setUse_disk(Integer.parseInt(parts2[4]));
                                    canNode.get(j).setData(parts2[5]); //将该数据存到该节点上
                                    LOGGER.info("SUCCESS! "+canNode.get(j).getId());
                                }
                            }



                            /**
                             * 由于这里不会发送到自己这里，需要判断其中是否有自己的地址
                             */
                            if(storeList.contains(InetAddress.getLocalHost().getHostAddress())){//是否包括本地
                                if(!uDataFile.exists()){
                                    FileUtils.writeStringToFile(uDataFile, request.substring(5)+" "+
                                            org.apache.commons.lang3.StringUtils.join(storeList,"/"), StandardCharsets.UTF_8, true);
                                }else {
                                    FileUtils.writeStringToFile(uDataFile, "\n"+request.substring(5)+" "+
                                            org.apache.commons.lang3.StringUtils.join(storeList,"/"), StandardCharsets.UTF_8, true);

                                }
                                uDataList.add(request.substring(5));//再添加进uDataList
                            }

                            //将数据发送各个节点，这些节点保存到本地，还有元数据的选择，原本的字符串后面加上ip
                            peerNetwork.broadcast("DATA "+request.substring(5)+" "+ org.apache.commons.lang3.StringUtils.join(storeList,"/"));
                            //元数据项的打包
                            Metadata metadata = new Metadata();
                            metadata.setDatatype(parts2[0]);
                            metadata.setTime(parts2[1]);
                            metadata.setLocation(parts2[2]);
                            metadata.setProducer(RSAUtils.privateEncrypt(parts2[3], RSAUtils.getPrivateKey(th.mapStr.get("privateKey"))));
                            metadata.setStoreList(storeList);
                            String[] vac = {gson.toJson(metadata)};//将这些数据变成json格式

                            /**
                             * 这个信息希望不仅是该节点收到
                             * 这里希望多个节点收到
                             */
                            String comm = "MINE";
                            for(int i = 0;i < vac.length;i++){
                                comm += (" "+vac[i]);
                            }
                            peerNetwork.broadcast(comm);
                            Thread.sleep(1000);

                            //根据vac创建新的区块
                            //进行广播，广播需要进行创建区块
                            //发送标志，就是敦促各个节点创建区块，争抢打包权
                            LOGGER.info("Fight for the right power, and attempting to add block: "+request.substring(5));
                            workProof = new WorkProof(blockChain.get(blockChain.size()-1), vac, nodeList.get(0), peerNetwork, dataFile);
                            Thread workThread = new Thread(workProof);
                            workThread.start();
                            th.res = "Mining......";


                        }catch (Exception e){
                            th.res = "Syntax (no '<' or '>'): send <vac> - Virtual Asset Count(Integer)";//出错了
                            LOGGER.error("Invalid vac - Virtual Asset Count(Integer)");
                        }
                    }else {
                        //不是send了，就俩命令，其他的都是非法的
                        th.res = "Unknown command: \""+parts[0]+"\" ";
                    }
                }
            }
            //结束远程线程
            TimeUnit.MILLISECONDS.sleep(100);//歇会

        }

    }
}


